package cn.itsource.basic.service.impl;

import cn.itsource.basic.constant.BaseConstants;
import cn.itsource.basic.dto.BinderDto;
import cn.itsource.basic.dto.LoginDto;
import cn.itsource.basic.exception.BusinessException;
import cn.itsource.basic.jwt.JwtUtils;
import cn.itsource.basic.jwt.PayLoadData;
import cn.itsource.basic.jwt.RsaUtils;
import cn.itsource.basic.service.ILoginService;
import cn.itsource.basic.util.AjaxResult;
import cn.itsource.basic.util.HttpClientUtils;
import cn.itsource.basic.util.MD5Utils;
import cn.itsource.basic.util.StrUtils;
import cn.itsource.org.domain.Employee;
import cn.itsource.org.mapper.EmployeeMapper;
import cn.itsource.system.domain.Menu;
import cn.itsource.system.mapper.MenuMapper;
import cn.itsource.system.mapper.PermissionMapper;
import cn.itsource.user.domain.Logininfo;
import cn.itsource.user.domain.User;
import cn.itsource.user.domain.Wxuser;
import cn.itsource.user.mapper.LogininfoMapper;
import cn.itsource.user.mapper.UserMapper;
import cn.itsource.user.mapper.WxuserMapper;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.security.PrivateKey;
import java.util.*;
import java.util.concurrent.TimeUnit;

/*
* 登录相关的业务
*
* */
@Service
public class LoginServiceImpl implements ILoginService {

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private WxuserMapper wxuserMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private EmployeeMapper employeeMapper;

    /*
    * 1.参数非空校验
    * 2.查询数据库 查询username phone email三列 用户是否存在
    * 2.1.不存在 直接抛用户名不存在
    * 2.2.存在 比对密码是否正确
    *   2.2.1.密码不正确 抛出密码错误
    *   2.2.2.密码正确
    *       生成UUID,即token 将登录信息存到redis中 设置30分钟过期
    *       返回token、logininfo
    * */
    @Override
    public HashMap<String, Object> account(LoginDto loginDto) {

//          1.参数非空校验
        if (StringUtils.isBlank(loginDto.getUsername())
                || StringUtils.isBlank(loginDto.getPassword())
                || StringUtils.isBlank(loginDto.getType())){
            throw new BusinessException("参数不能为空");
        }
        /**
         * 2.查询数据库 查询username phone email三列 用户是否存在
         *  这里可以在t_logininfo表中故意添加两个冗余字段：
         *      user_id：当logininfo的type=0时，它表示后台员工id；当前logininfo的type=1时，它表示前台用户id
         *      shop_id：当logininfo的type=0时，额外冗余它所属的店铺id；当logininfo的type=1时，此字段设置为null
         *  这样做的好处是：
         *      我登录的时候的查询了logininfo对象，就不必再去查询t_employee或者t_user表了，减少查询次数，提高效率；
         *  但是，冗余了这2个字段后，商家入驻和前台用户注册业务需要修改：
         *      商家入驻：t_shop、t_employee、t_logininfo，在最后往t_logininfo表添加数据的时候多添加两个字段值就行了；
         *      前台用户注册：t_user、t_logininfo，同样也是在往t_logininfo表添加数据的时候多添加一个user_id即可。
         */
        Logininfo logininfo = logininfoMapper.loadByAccountAndType(loginDto.getUsername(),loginDto.getType());
//          2.1.不存在 直接抛用户名不存在
        if (logininfo == null){
            throw new BusinessException("用户名不存在");
        }
//          2.2.存在 比对密码是否正确
        if (!logininfo.getPassword().equals(MD5Utils.encrypByMd5(loginDto.getPassword() + logininfo.getSalt())  )){//加密 加盐
            //            2.2.1.密码不正确 抛出密码错误
            throw new BusinessException("密码不正确");
        }

//            2.2.2.密码正确
//                生成UUID,即token 将登录信息存到redis中 设置30分钟过期
       /* String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
//                返回token、logininfo
        HashMap<String, Object> map = new HashMap<>();
        map.put("token",token);
        //置空敏感信息
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        map.put("logininfo",logininfo);*/
        return loginSuccessHandler(logininfo);

    }

    /*
    * 处理登录成功
    * 1.查询出用户拥有的权限和菜单
    * 2。使用jwt工具类将用户信息、菜单、权限生成jwt字符串 这个字符串也把它叫做token 返回给前端
    * */
    private HashMap<String, Object> loginSuccessHandler(Logininfo logininfo) {

//        1.查询出用户拥有的权限和菜单
        PayLoadData payLoadData = new PayLoadData();
        payLoadData.setUser(logininfo);
        if (!logininfo.getType()) {//后台用户才会查权限 查菜单
            //如果是后台用户：还需要查询它所属的员工对象(店铺管理员)
            Employee admin = employeeMapper.findByLoginInfoId(logininfo.getId());
            logininfo.setAdmin(admin);

            List<String> permissions = permissionMapper.loadPermissionByLonginId(logininfo.getId());
            //因为前端只渲染了两层菜单 所以我们这里可以只查两层
            List<Menu> menus = menuMapper.loadMenuByLoginId(logininfo.getId());
            //封装用户信息 权限 菜单
            payLoadData.setMenus(menus);
            payLoadData.setPermissions(permissions);
        }else{
            //如果是前台用户，还需要查询User用户对象
            //User user = userMapper.findByLoginInfoId(logininfo.getId());
        }

        try {
            //私钥加密
            PrivateKey privateKey = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader()
                    .getResource("hrm_auth_rsa").getFile());
            String jwt_token = JwtUtils.generateTokenExpireInMinutes(payLoadData, privateKey, 720);
            HashMap<String, Object> map = new HashMap<>();
            map.put("token",jwt_token);
            //因为前端没有解密jwt_token的代码 所以我们还是得把明文的permissions、menus、logininfo返回回去
            logininfo.setSalt(null);
            logininfo.setPassword(null);
            map.put("menus",payLoadData.getMenus());
            map.put("permissions",payLoadData.getPermissions());
            map.put("logininfo",logininfo);
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }


    /*
    * 微信登录：
    * 1.参数校验
    * 2.通过code获取access_token和openid
    * 3.通过openid查数据库是否已经微信登录过
    *   3.1 有 则直接登录
    *   3.2 没有 则跳转到绑定页面
    *
    * */
    @Override
    public AjaxResult wechat(Map<String, String> map) {
//        1.参数校验
        String code = map.get("code");
        if (StringUtils.isBlank(code)){
            throw new BusinessException("参数不能为空");
        }
//        2.通过code获取access_token和openid
        String url = BaseConstants.WxConstants.token_url.replace("APPID",BaseConstants.WxConstants.APPID)
                .replace("SECRET",BaseConstants.WxConstants.SECRET)
                .replace("CODE",code);
        //向微信发起请求 获取access_token和openid
        String tokenAndOpenid = HttpClientUtils.httpGet(url);
        //把返回的json字符串转换为jason对象
        JSONObject jsonObject = JSONObject.parseObject(tokenAndOpenid);
        String openId = jsonObject.getString("openid");
        String accessToken = jsonObject.getString("access_token");
//        3.通过openid查数据库是否已经微信登录过
        Wxuser wxuser = wxuserMapper.loadByOpenId(openId);
        if (wxuser != null && wxuser.getUserId() != null){
            Logininfo logininfo = logininfoMapper.loadByUserId(wxuser.getUserId());
            //        3.1 有 则直接登录
            //                生成UUID,即token 将登录信息存到redis中 设置30分钟过期
            String token = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
//                返回token、logininfo
            HashMap<String, Object> mapResult = new HashMap<>();
            mapResult.put("token",token);
            //置空敏感信息
            logininfo.setSalt(null);
            logininfo.setPassword(null);
            mapResult.put("logininfo",logininfo);
            return AjaxResult.me().setResultObj(mapResult);
        }
//        3.2 没有 则跳转到绑定页面
        String binderParam = "?openId="+openId+"&accessToken="+accessToken;
        return AjaxResult.me().setSuccess(false).setMessage("binder").setResultObj(binderParam);
    }


    /*
    * 绑定的业务
    * 1.参数校验
    * 2.手机验证码校验
    * 3.使用phone查询数据库是否存在该用户
    *   3.1 不存在  初始化user和logininfo 保存数据库
    *   3.2 存在
    * 4.查询用户的微信信息
    * 5.将用户信息插入wxuser
    * 6.登录并返回登录信息
    *
    * */
    @Override
    public HashMap<String, Object> binder(BinderDto binderDto) {
//        1.参数校验
        String accessToken = binderDto.getAccessToken();
        String openId = binderDto.getOpenId();
        String phone = binderDto.getPhone();
        String verifyCode = binderDto.getVerifyCode();
        if (StringUtils.isBlank(accessToken)
                || StringUtils.isBlank(openId)
                || StringUtils.isBlank(phone)
                || StringUtils.isBlank(verifyCode)){
            throw new BusinessException("参数不能为空");
        }
//        2.手机验证码校验
        Object smsCodeAndTime = redisTemplate.opsForValue().get(BaseConstants.VerifyCodeConstants.USER_PHONE_BINDER_PREFIX + phone);//9527:4894978
        if (smsCodeAndTime == null){
            throw new BusinessException("验证码已过期");
        }
        String[] smsCodeAndTimeArr = smsCodeAndTime.toString().split(":");
        if (!verifyCode.equalsIgnoreCase(smsCodeAndTimeArr[0])){
            throw new BusinessException("手机验证码错误");
        }
//        3.使用phone查询数据库是否存在该用户
        User user = userMapper.loadByPhone(phone);
        Logininfo logininfo = null;
//        3.1 不存在  初始化user和logininfo 保存数据库
        if (user == null){
            user = initUser(binderDto);
            logininfo = user2Login(user);
            logininfoMapper.save(logininfo);
            user.setLogininfoId(logininfo.getId());
            userMapper.save(user);
        }else {
            //    3.2 存在
            logininfo = logininfoMapper.loadByUserId(user.getId());
        }

//        4.查询用户的微信信息
        String url = BaseConstants.WxConstants.USERINFO_URL.replace("ACCESS_TOKEN",accessToken)
                .replace("OPENID",openId);
        String userInfoStr = HttpClientUtils.httpGet(url);
//        5.将用户信息插入wxuser
        Wxuser wxuser = JSONObject.parseObject(userInfoStr, Wxuser.class);//将返回的jason字符串转换成Wxuser
        wxuser.setUserId(user.getId());
        wxuserMapper.save(wxuser);
//        6.登录并返回登录信息
        //                生成UUID,即token 将登录信息存到redis中 设置30分钟过期
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
//                返回token、logininfo
        HashMap<String, Object> mapResult = new HashMap<>();
        mapResult.put("token",token);
        //置空敏感信息
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        mapResult.put("logininfo",logininfo);

        return mapResult;
    }


    private Logininfo user2Login(User user) {
        Logininfo logininfo = new Logininfo();
        //使用对象拷贝的方法
        BeanUtils.copyProperties(user,logininfo);
        logininfo.setDisable(false);//默认账号启用
        logininfo.setType(true); //true 1 :用户    false 0:员工
        return logininfo;
    }


    private User initUser(BinderDto binderDto) {
        User user = new User();
        user.setPhone(binderDto.getPhone());
        String salt = StrUtils.getComplexRandomString(32);
        user.setSalt(salt);
        /*这里你可以给以初始密码 给用户发一个段 你的初始密码是xxx*/
        user.setPassword(MD5Utils.encrypByMd5("1" + salt));//用户的密码需要加密加盐
        user.setCreatetime(new Date());
        user.setState(1);
        user.setUsername(binderDto.getPhone());
        return user;
    }


}
